package client;
import common.*;
import java.net.*;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;

//The network client is responsible for all networking
//related communication with the server.

public class NetworkClient extends Observable implements Observer {
	private Socket s;
	private DataOutputStream so;
	private DataInputStream si;
	
	private Queue<Sendable> outQ;
	
	private volatile boolean failed = false;
	private volatile boolean started = false;
	private volatile IOException IO = null;
	
	private final Thread T;
	static final int CONNECT_TIMEOUT = 10000;
	static final int SOCKET_TIMEOUT = 1000;
	static final int SLEEP_TIME = 50;
	
	public NetworkClient (String adr, int port) throws IOException {
		this(InetAddress.getByName(adr), port);
	}
	public NetworkClient (final InetAddress adr, final int port) {
		outQ = new ConcurrentLinkedQueue<Sendable>();
		final NetworkClient nc = this;
		T = new Thread(new Runnable(){
			public void run() {
				try {
					s = new Socket();

					s.setSoTimeout(CONNECT_TIMEOUT);
					//s.setTcpNoDelay(true);
					s.connect( new InetSocketAddress( adr, port) );
					
					
					so = new DataOutputStream(s.getOutputStream());
					si = new DataInputStream(s.getInputStream());
					s.setSoTimeout(SOCKET_TIMEOUT);
					
					started = true;
					
					while (!T.isInterrupted() && s.isConnected()) {
						
						Sendable incoming = decode();
						Sendable outgoing = (outQ.isEmpty()) ? null : outQ.remove();
						if (incoming != null) {
							    //System.out.println("Received from server: " + incoming.getType() );
								nc.setChanged();
								nc.notifyObservers(incoming);
						}
						if (outgoing != null) encode(outgoing);
						
						//Sleep
						try{ if (outgoing == null && incoming == null) Thread.sleep(SLEEP_TIME); } catch (Exception e){};
					}
				} catch (IOException e) {
					IO = e;
					failed = true;
				}
				nc.setChanged();
				nc.notifyObservers("Dead");
				
				System.err.println("Client NetworkThread is dead : " + IO);
				//if ((IO instanceof SocketException)) IO.printStackTrace();
			}
		});
	}
	//Various messages with sendables to send out
	//arrive here.
	public void update (Observable o, Sendable arg) {
		outQ.add(arg);
	}
	public void update (Observable o, Object arg) {
		if (arg instanceof Sendable) update(o,(Sendable)arg);
	}
	
	
	
	//Started?
	public boolean started() {
		return started;
	}
	//Is there a problem?
	public boolean failed() {
		return failed;
	}
	//Returns any failures.
	public Exception failure() {
		return IO;
	}
	
	//Close connection with server.
	public void close() throws IOException {
		T.interrupt();
		s.close();
	}
	
	//Start the connection with the server.
	public void start() {
		T.start();
	}
	
	//Encoding/Decoding of messages
	
	//Encodes and writes a sendable to send to the server.
	private boolean encode(Sendable s) throws IOException {	
		
			byte [] out = null;
			try {
				out = AllTheEvilsOfThisWorld.sendableToByteArray(s);
			} catch (Throwable t) {
				System.err.println("NetworkClient.encode()");
				t.printStackTrace();
			}
			so.writeInt(out.length);
			so.write(out);
			System.err.println("Client Sends : " + s.getType());
		
		
		return true;
	}
	//Decodes a sendable from the server.
	private Sendable decode() throws IOException {
			if (si.available() == 0) return null;
			
			s.setSoTimeout(0);
			int i =  si.readInt();
			byte [] b = new byte [i];
			for (int j = 0; j<i && j>=0; j+= si.read(b,j,b.length-j));
			
			s.setSoTimeout(SOCKET_TIMEOUT);
			
			try {
				return AllTheEvilsOfThisWorld.byteArrayToSendable(b); 
			} catch (Throwable t) {
				System.err.println("NetworkClient.decode() Throwable");
				t.printStackTrace();
			}
		return null;
	}
	
}
